﻿using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using UIOC.CodeAnalysis.Extensions;
using UIOC.CodeAnalysis.Helpers;

namespace UIOC.CodeAnalysis.Generators;

internal static class ImplementationGenerator {

    public readonly struct GeneratedInfo(GeneratedSource source, INamedTypeSymbol objectSymbol) {

        public GeneratedSource GeneratedSource { get; } = source;

        public INamedTypeSymbol ObjectSymbol { get; } = objectSymbol;
    }

    public const string IMPLEMENTATION_TYPE_SUFFIX = "__GeneratedImplementation";

    private static void LogInfo(string message) {
        SourceOutputHelper.LogInfo(DiagnosticSeverity.Info, $"[{nameof(ImplementationGenerator)}] {message}");
    }

    public static ImmutableDictionary<INamedTypeSymbol, GeneratedInfo> GenerateImplementation(
        Compilation compilation,
        ImmutableArray<ClassDeclarationSyntax> classDeclarationSyntaxes
    ) {
        var implementAttributeSymbol = compilation.GetTypeByMetadataName(AttributeFullNames.Implement);
        if (implementAttributeSymbol == null) {
            return ImmutableDictionary<INamedTypeSymbol, GeneratedInfo>.Empty;
        }

        LogInfo($"[Assembly({compilation.Assembly.Name})] {nameof(GenerateImplementation)} ...");

        var result = ImmutableDictionary.CreateBuilder<INamedTypeSymbol, GeneratedInfo>(SymbolEqualityComparer.Default);

        var classSymbols = classDeclarationSyntaxes
            .Select(syntax => compilation.GetSemanticModel(syntax.SyntaxTree).GetDeclaredSymbol(syntax))
            .OfType<INamedTypeSymbol>()
            .Distinct(SymbolEqualityComparer.Default)
            .Cast<INamedTypeSymbol>()
            .ToList();
        foreach (var classSymbol in classSymbols) {
            var implementAttributeData = classSymbol.GetAttributes(implementAttributeSymbol).FirstOrDefault();
            if (implementAttributeData == null) {
                continue;
            }
            var (compilationUnitSyntax, namespaceName) = GenerateImplementationCompilationUnit(classSymbol, compilation);
            if (compilationUnitSyntax == null) {
                continue;
            }
            var outputSource = SourceText.From(compilationUnitSyntax.NormalizeWhitespace().ToFullString(), Encoding.UTF8);
            var hintName = classSymbol.GetShortName().Replace("<", "(").Replace(">", ")");
            result.Add(classSymbol, new GeneratedInfo(
                new GeneratedSource(
                    hintName: hintName,
                    assemblyName: compilation.Assembly.Name,
                    namespaceName: namespaceName,
                    sourceText: outputSource,
                    compilationUnitSyntax: compilationUnitSyntax,
                    generatorName: nameof(ImplementationGenerator)
                ),
                classSymbol
            ));
        }
        return result.ToImmutable();
    }

    private static (CompilationUnitSyntax CompilationUnitSyntax, string NamespaceName) GenerateImplementationCompilationUnit(
        INamedTypeSymbol classSymbol,
        Compilation compilation
    ) {
        var className = $"{classSymbol.Name}{IMPLEMENTATION_TYPE_SUFFIX}";

        var ifDirectiveSyntax = SyntaxFactory.IfDirectiveTrivia(
            SyntaxFactory.IdentifierName("!DISABLE_DEBUGGER_ATTRIBUTE"),
            isActive: true,
            branchTaken: true,
            conditionValue: true
        );
        var endIfDirectiveSyntax = SyntaxFactory.EndIfDirectiveTrivia(isActive: true);

        var classTypeSyntax = classSymbol.CreateSimpleBaseTypeSyntax();
        //LogInfo($"{nameof(classTypeSyntax)}={classTypeSyntax}");
        var classDeclarationSyntax = SyntaxFactory.ClassDeclaration(className)
            .AddModifiers(
                SyntaxFactory.Token(SyntaxKind.PrivateKeyword),
                SyntaxFactory.Token(SyntaxKind.SealedKeyword)
            )
            .AddBaseListTypes(classTypeSyntax)
            .AddAttributeLists([
                SyntaxFactory.AttributeList(SyntaxFactory.SeparatedList([
                    SyntaxFactory.Attribute(SyntaxFactory.QualifiedName(
                        SyntaxFactory.IdentifierName($"global::{nameof(System)}.{nameof(System.Diagnostics)}"),
                        SyntaxFactory.IdentifierName(nameof(System.Diagnostics.DebuggerStepThroughAttribute))
                    )),
                ])).WithLeadingTrivia(
                    SyntaxFactory.Trivia(ifDirectiveSyntax),
                    SyntaxFactory.CarriageReturnLineFeed
                ),
                SyntaxFactory.AttributeList(SyntaxFactory.SeparatedList([
                    SyntaxFactory.Attribute(SyntaxFactory.QualifiedName(
                        SyntaxFactory.IdentifierName($"global::{nameof(System)}.{nameof(System.Diagnostics)}"),
                        SyntaxFactory.IdentifierName(nameof(System.Diagnostics.DebuggerNonUserCodeAttribute))
                    )),
                ])).WithTrailingTrivia(
                    SyntaxFactory.CarriageReturnLineFeed,
                    SyntaxFactory.Trivia(endIfDirectiveSyntax)
                )
            ]);

        if (classSymbol.TypeParameters.Length > 0) {
            classDeclarationSyntax = classDeclarationSyntax.AddTypeParameterListParameters([.. classSymbol.TypeParameters.Select(tp => SyntaxFactory.TypeParameter(tp.Name))]);
            classDeclarationSyntax = classDeclarationSyntax.AddConstraintClauses([.. classSymbol.GetConstraintClauses()]);
        }

        var registrationClassDeclarationSyntax = SyntaxFactory.ClassDeclaration(TypeRegistrationGenerator.CLASS_NAME_TYPE_REGISTRATIONS)
               .AddModifiers(
                   SyntaxFactory.Token(SyntaxKind.InternalKeyword),
                   SyntaxFactory.Token(SyntaxKind.StaticKeyword),
                   SyntaxFactory.Token(SyntaxKind.PartialKeyword)
               )
               .AddMembers(classDeclarationSyntax);

        var leadingTriviaSyntax = SyntaxFactory.Comment(UIOCGenerator.Leading);
        var namespaceName = compilation.Assembly.Name;
        var namespaceDeclarationSyntax = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.IdentifierName(namespaceName))
            .WithLeadingTrivia(leadingTriviaSyntax)
            .AddMembers(registrationClassDeclarationSyntax);

        var compilationUnit = SyntaxFactory.CompilationUnit()
            .AddMembers(namespaceDeclarationSyntax);

        return (compilationUnit, $"{namespaceName}__{TypeRegistrationGenerator.CLASS_NAME_TYPE_REGISTRATIONS}");
    }
}
